package net.babeuf.form 
{
	import flash.events.EventDispatcher;
	import flash.net.URLVariables;
	import net.babeuf.form.conditions.AbstractCondition;
	import net.babeuf.form.errors.FieldError;
	import net.babeuf.form.event.FieldErrorEvent;
	import net.babeuf.form.event.FieldEvent;
	import net.babeuf.form.factory.MetaFormFactory;
	import net.babeuf.form.fields.AbstractField;
	import net.babeuf.form.messages.Messages;
	
	/**
	 * Form 
	 * Provides an auto generating and validating tool for flash forms
	 * @author damien pegoraro
	 */
	public class Form extends EventDispatcher
	{		
		/**
		* Dispatched when en error is handled for the current form
		* @eventType net.babeuf.form.event.FieldErrorEvent.FIELD_ERROR
		*/
		[Event(name = "fieldError", type = "net.babeuf.form.event.FieldErrorEvent")]
		
		
		protected var _fields : Array ; 	
		protected var _conditions : Array ; 		
		protected var _messager : Messages ; 
		protected var _booleanRewrite : Object ; 
		protected var _tabIndex : int = 1 ; 		
		protected var _validateOnFocusOut : Boolean ; 
		
		public function Form( validateOnFocusOut  : Boolean = false ) 
		{
			_fields = new Array() ; 
			_conditions = new Array() ; 
			_messager = new Messages() ; 
			_validateOnFocusOut = validateOnFocusOut ; 
		}
		
		public function setValidateOnFocusOut( value : Boolean = false ) : void
		{
			_validateOnFocusOut = value ;
		}
		
		/**
		 * Push a new field to the form
		 * @param	field  the field to add
		 * @param	id  the field ID
		 */
		public function addField(   field : AbstractField, id : String) : AbstractField
		{
			field.name = id ; 
			_tabIndex = field.setTabIndex(_tabIndex) ; 
			field.addEventListener( FieldErrorEvent.FIELD_ERROR, _fieldErrorHandler ) ; 		
			field.addEventListener( FieldEvent.FOCUS_OUT, _fieldFocusOut ) ; 
			_fields.push(field) ; 
			return field ; 
		}
		
		/**
		 * Returns a field from its ID
		 * @param	id the field ID
		 * @return  the corresponding field
		 */
		public function getField( id : String ) : AbstractField 
		{
			for each ( var f : AbstractField in _fields ) if ( f.name == id ) return f ; 
			return null ; 
		}
		
		/**
		 * Add an extra validating condition to the form 
		 * @param	condition the condition to match
		 * @param	id  the condition ID
		 * @param	errorCode  an error code to return when this condition is not matched
		 */
		public function addCondition( condition : AbstractCondition, id : String, errorCode : String ) : void
		{
			condition.errorCode = errorCode ; 
			condition.name = id ; 
			condition.addEventListener( FieldErrorEvent.FIELD_ERROR, _fieldErrorHandler ) ; 			
			_conditions.push( condition ) ; 
		}
		
		/**
		 * Remove all events and references for garbage collection
		 */
		public function dispose() : void
		{
			if ( _fields && _fields.length > 0 ) 
			{
					for each ( var field : AbstractField in _fields ) 
				{
					if ( field ) 
					{
						field.removeEventListener( FieldErrorEvent.FIELD_ERROR, _fieldErrorHandler ) ; 		
						field.removeEventListener( FieldEvent.FOCUS_OUT, _fieldFocusOut ) ; 
						field.dispose() ; 
					}
				}
			}
			
			if ( _conditions && _conditions.length > 0 )
			{
				for each ( var cond : AbstractCondition in _conditions )
				{
					if ( cond )
					{
						cond.removeEventListener( FieldErrorEvent.FIELD_ERROR, _fieldErrorHandler ) ; 	
						cond.dispose() ; 
					}
				}				
				
			}
			
			//_messager.dispose() ; 			
			_fields = null ; 
			_conditions = null ;
			_messager = null ; 
		}
		
		/**
		 * Returns a condition from its ID
		 * @param	id the condition ID
		 * @return the corresponding condition
		 */
		public function getCondition( id : String ) : AbstractCondition 
		{
			for each ( var c : AbstractCondition in _conditions ) if ( c.name == id ) return c ; 
			return null ; 
		}
		
		/**
		 * Reset all fields and conditions of the form
		 */
		public function reset() : void
		{
			empty()  ;
		}
		
		/**
		 * Reset all fields and conditions of the form
		 */
		public function empty() : void
		{
			for each ( var f : AbstractField in _fields ) {	f.empty() ; }
			for each ( var c : AbstractCondition in _conditions ) {	c.empty() ; }
		}
		
		/**
		 * The error messager
		 */
		public function get messager()  : Messages
		{
			return _messager ; 
		}
		
		public function get tabIndex():int 
		{
			return _tabIndex;
		}
		
		public function set tabIndex(value:int):void 
		{
			_tabIndex = value;
		}
		
		/**
		 * Validates the current form
		 * @return An array of FieldError that contains all found errors
		 */
		public function validate( maxErrors : int = 0 ) : Array
		{
			var out : Array = new Array() ; 
			var errs : int = 0 ; 
			for each ( var f : AbstractField in _fields ) 
			{
				if (  !f.validate() ) 
				{
					var err: FieldError = f.getErrorCode() ; 
					err.message  = _messager.get( err.code, err.field ) ; 					
					out.push(err) ;
					errs ++ ; 
					if ( maxErrors > 0 && errs >= maxErrors ) return out ; 
				}
			}
			
			for each ( var c : AbstractCondition in _conditions ) 
			{
				if (  !c.match() ) 
				{
					out.push( new FieldError(null, c.errorCode, _messager.get( c.errorCode, null )) ) ;	
					errs ++ ; 
					if ( maxErrors > 0 && errs >= maxErrors ) return out ; 
				}
			}
			
			return out ; 
		}
		
		/**
		 * Return values of every form field formatted to Object
		 * @param	includeEmpty if true, empty form values will be returned as empty fields
		 * @return values of every form field formatted to Object
		 */
		public function values( includeEmpty : Boolean = false ) : Object 
		{
			var out : Object = new Object() ; 
			for each ( var field : AbstractField in _fields ) 
			{
				if ( field.notSent ) continue ; 
				
				var value : * = field.getValue() ; 
				if (  value || includeEmpty || typeof value == "boolean" ) 
				{
					switch ( typeof value ) 
					{
						case "boolean" : 						
							if ( _booleanRewrite  )  out[field.name] = value ? _booleanRewrite.valueTrue : _booleanRewrite.valueFalse ; 
							else  out[field.name] = value ;  break ; 					
						break ; 
						default :  out[field.name] = value ;  break ; 
					}
				}
			}			
			return out ; 
		}
		
		/**
		 * Return values of every form field formatted to URLVariables
		 * @param	includeEmpty if true, empty form values will be returned as empty fields
		 * @return values of every form field formatted to URLVariables
		 */
		public function valuesAsURLVariables( includeEmpty : Boolean = false ) : URLVariables 
		{
			var o  : Object = values( includeEmpty ) ; 
			var out : URLVariables = new URLVariables() ; 			
			for ( var i : String in o ) out[i] = o[i] ; 		
			return out ; 
		}
		
		
		/**
		 * Automatically rewrite all Boolean values on values() call  for more convenient use
		 * @param	valueTrue value for true
		 * @param	valueFalse value for false
		 */
		public function setBooleanRewriteRule( valueTrue : * = null, valueFalse : * = null ) : void
		{
			_booleanRewrite = ( valueTrue || valueFalse ) ? new Object() : null ; 
			
			if (  valueTrue ) _booleanRewrite.valueTrue = valueTrue ; 
			if (  valueFalse ) _booleanRewrite.valueFalse = valueFalse ; 
			
		}
		
		public function initFromMetadata( object : * ) : void
		{
			MetaFormFactory.buildFromMetas( object, this) ; 
		}
		
		
		
		protected function _fieldErrorHandler(e:FieldErrorEvent):void 
		{
			e.error.message = _messager.get( e.error.code, e.error.field )  ; 			
			dispatchEvent( e.clone() ) ; 
		}
		
		protected function _fieldFocusOut( e : FieldEvent ) : void
		{
			if ( e.field.validateOnFocusOut || _validateOnFocusOut ) 
			{
				e.field.autoValidate() ; 				
			}
			
		}
		
		
	}

}